---
category: More
title: Migration Guides
order: 2
---

## Threlte 8

Threlte 8 adds Svelte 5 support and removes Svelte 4 support.

This upgrade contains bug fixes, better average performance, smaller bundle size, and an improved development experience.

There are a few notable breaking changes listed below.

### Automatic Disposal

[Automatic disposal](/docs/learn/basics/disposing-objects) has been improved to
only dispose of objects that are referenced by a `<T>` component. Objects are no
longer scanned recursively for disposable objects.

```svelte
<script>
  import { T } from '@threlte/core'
  import { useTexture } from '@threlte/extras'

  const map = useTexture('/some/texture.png')
</script>

{#if $map}
  <T.Mesh>
    <T.BoxGeometry />
    <T.MeshBasicMaterial map={$map} />
  </T.Mesh>
{/if}
```

In this example, Threlte 7 also disposed of the texture when the material
unmounted. This is no longer the case in Threlte 8. This change is introduced to
improve performance and to make the behavior of automatic disposal more
intuitive. When looking at simple examples like the one above, this might seem
like a regression, but with scale the previous approach of deeply recursive
automatic disposal was hard to reason about and a performance bottleneck.

### Plugin API

The plugin API has been changed to allow for greater granularity and a reactivity model that is in-line with Svelte 5.

#### `createPlugin` has been removed

Threlte 7 included a function called `createPlugin` that allowed to separate a
plugin declaration from its implementation. The recommended way to create
plugins is to export a function that invokes `injectPlugin`:

```ts
import { injectPlugin } from '@threlte/core'

export const createSomePlugin = (pluginArg: string) => {
  injectPlugin('some-plugin', () => {
    // ... Plugin Code
  })
}
```

This plugin can now be implemented like this:

```svelte title="Scene.svelte"
<script>
  import { createSomePlugin } from '$plugins'

  createSomePlugin('plugin-arg')
</script>
```

#### Plugin callbacks have been removed

In Threlte 7, plugins could return an object with several callback functions
that were invoked when:

- `ref` changed
- any prop changed (e.g. `makeDefault`, `dispose`, `attach`, etc.)
- any "rest" prop changed (e.g. `position`, `color`, etc.)

```ts title="Threlte 7"
injectPlugin('some-plugin', ({ ref, props }) => {
  return {
    onRefChange(newRef) {
      // ...
    },
    onPropsChange(props) {
      // ...
    },
    onRestPropsChange(restProps) {
      // ...
    }
  }
})
```

These callbacks have been removed. Instead, you can use the first argument of the plugin
callback, which is a reactive object containing all properties needed:

```ts title="Threlte 8"
injectPlugin('some-plugin', (args) => {
  args.ref
  args.makeDefault
  args.args
  args.attach
  args.manual
  args.makeDefault
  args.dispose
  args.props // All other props declared on the component
})
```

The `args` object is reactive and will update whenever any of the referenced values change.

<Tip type="info">
  A plugin may still return an object with `pluginProps` to specify which props
	the `<T>` component should not react to.
</Tip>

### Events

Events will no longer work, and have been replaced with callback props.

```svelte {2,3}m
<T.Mesh
  onclick={onClick}
  onpointerenter={onPointerEnter}
>
  <T.BoxGeometry />
  <T.MeshStandardMaterial />
</T.Mesh>
```

The signature of the `oncreate` callback prop has changed. Instead of receiving an object with a cleanup function,
you may now return a cleanup function that will run when the object is destroyed or its args change. This is more in-line with other apis in svelte 5 and threlte.

```svelte
<T.Mesh
  oncreate={(ref) => {
    return () => {
      console.log('cleanup')
    }
  }}
>
  <T.BoxGeometry />
  <T.MeshStandardMaterial />
</T.Mesh>
```

The `createRawEventDispatcher` and `forwardEventHandlers` exports will no longer work.

Instead of dispatching events with `createRawEventDispatcher`, invoke callback props.

```svelte
<script lang="ts">
  import { T } from '@threlte/core'
  import { OrbitControls } from '@threlte/extras'

  type Props = {
    onchange?: () => void
  }

  let { onchange }: Props = $props()

  onchange?.()
</script>

<T.PerspectiveCamera makeDefault>
  <OrbitControls {onchange} />
</T.PerspectiveCamera>
```

Instead of using `forwardEventHandlers`, pass rest props
to the component you wish to forward events to.

```svelte
<script>
  let { ...rest } = $props()
</script>

<T.Mesh {...rest}>
  <T.BoxGeometry />
  <T.MeshBasicMaterial />
</T.Mesh>
```

This will pass the new callback props mentioned in the previous section down the tree of components.

### Attach API & Trait Components

The signature and heuristic of the attach API of the `<T>` component has
changed. The trait components `<HierarchicalObject>` and `<SceneGraphObject>`
have been removed.

#### `attach` Function Signature

```svelte
<!-- Threlte 7 -->
<T.Mesh
  attach={(parent, self) => {
    console.log('attaching', parent, self)
    return () => {
      console.log('detaching', parent, self)
    }
  }}
/>

<!-- Threlte 8 -->
<T.Mesh
  attach={({ ref, parent, parentObject3D }) => {
    console.log('attaching', ref, parent, parentObject3D)
    return () => {
      console.log('detaching', ref, parent, parentObject3D)
    }
  }}
/>
```

#### `attach={false}`

If `false` is passed to the `attach` prop, the component will not be automatically attached to
the parent object. This is useful if you want to attach the component manually.

```svelte
<!-- "Dangling" component -->
<T
  is={mesh}
  attach={false}
/>
```

#### `attach={object3D}`

If an object3D instance is passed to the `attach` prop, the component will be attached to the instance, essentially acting as a portal.

<Tip type="warning">Be aware that the component still acts in the given context of the parent.</Tip>

```svelte
<!-- Attached to the provided object -->
<T
  is={mesh}
  attach={object3D}
/>
```

### Snippets

Slot props will no longer work, and must be replaced with snippets.
For example, the following components from Threlte 7 would need to be migrated from this:

```svelte
<T.PerspectiveCamera let:ref>
  <T.OrbitControls
    args={[ref, renderer.domElement]}
    on:change={invalidate}
  />
</T.PerspectiveCamera>
```

...to this:

```svelte
<T.PerspectiveCamera>
  {#snippet children({ ref })}
    <T.OrbitControls
      args={[ref, renderer.domElement]}
      onchange={invalidate}
    />
  {/snippet}
</T.PerspectiveCamera>
```

Any component that previously exposed a slot prop using the `let:` directive can follow this new pattern.

### Canvas component `size` prop

The `size` property on the `<Canvas>` component that allowed setting specific pixel dimensions has been removed.
To set a specific size of your `<Canvas>`, simply wrap it in an HTML element with your desired dimensions.

```svelte
<div style="width: 500px; height: 300px;">
  <Canvas>
    <Scene />
  </Canvas>
</div>
```

### Canvas component `rendererParameters` prop

The `renderParameters` canvas prop has been replaced with a more powerful `createRenderer` function.

If you need to manually set renderer parameters, call the function and return a renderer.

```svelte
<Canvas
  createRenderer={(canvas) => {
    return new WebGLRenderer({
      canvas,
      alpha: true,
      powerPreference: 'high-performance',
      antialias: false,
      depth: false,
      premultipliedAlpha: false
    })
  }}
>
  <Scene />
</Canvas>
```

Any Three renderer can be returned when calling `createRenderer`.

### Transitions

The transitions plugin currently does not work, we're working towards a new
transition system.

### `useGltf` and `<GLTF>`

`useGltf` and `<GLTF>` no longer contain a built-in `DracoLoader`, `KTX2Loader`, or `MeshoptDecoder`.
Instead, separate hooks can be imported and passed to these tools, improving their bundle size and flexibility.

```ts
import { useGltf, useDraco, useKtx2, useMeshopt } from '@threlte/extras'

const dracoLoader = useDraco()
const ktx2Loader = useKtx2()
const meshoptDecoder = useMeshopt()

const gltf = useGltf('./path/to/model.glb', {
  dracoLoader,
  ktx2Loader,
  meshoptDecoder
})
```

For more information, see the [`useGltf`](/docs/reference/extras/use-gltf) and [`GLTF`](/docs/reference/extras/gltf) docs.

### Rapier

#### Two Stage Physics

In order to enable fixed frame physics, the Rapier package is introducing two
[scheduler stages](/docs/learn/basics/scheduling-tasks#stages). So in the most
simple physics implementation:

```svelte
<Canvas>
  <World>
    <Scene />
  </World>
</Canvas>
```

The scheduler plan will look like this:

```txt
scheduler
├─ threlte-main-stage
├─ simulation
│  └─ simulation
├─ synchronization
│  └─ synchronization
└─ threlte-render-stage
```

[Tasks](/docs/learn/basics/scheduling-tasks#tasks) that are added to the
`simulation` stage will be executed according to the set
[framerate](/docs/reference/rapier/framerate), i.e. the delta provided in these
tasks corresponds to the delta time between physics frames. Tasks added to the
`synchronization` stage will be executed after all tasks of the `simulation`
stage have been executed, the delta is the regular `requestAnimationFrame` frame
delta. The stages and tasks are available as part of the `RapierContext` with the `useRapier` hook:

```ts
import { useTask } from '@threlte/core'
import { useRapier } from '@threlte/rapier'

const { simulationTask } = useRapier()

useTask(
  () => {
    // E.g. interact with the physics world here
  },
  {
    before: simulationStage
  }
)
```

#### BasicPlayerController has been removed

The `BasicPlayerController` component has been removed. If you need a player
controller, Rapier comes with a pre-made, easy to implement [Character
Controller](https://rapier.rs/docs/user_guides/javascript/character_controller).
It's more powerful and flexible than the old component.

The reason is stated in rapier's documentation as well:

> Despite the fact that this built-in character controller is designed to be
> generic enough to serve as a good starting point for many common use-cases,
> character-control (especially for the player's character itself) is often very
> game-specific. Therefore the builtin character controller may not work
> perfectly out-of-the-box for all game types.

#### `oncreate` event signature

The `oncreate` event signature available on `<RigidBody>`, `Collider` and
`<AutoColliders>` has been adapted to match the `oncreate` prop on `<T>`.

```svelte
<RigidBody
  oncreate={(ref) => {
    // ref is the created RigidBody instance
    return () => {
      // cleanup function
    }
  }}
>
  <!-- ... -->
</RigidBody>
```

## Threlte 7

Threlte 7 introduces a new _Task Scheduling System_ that allows you to easily
orchestrate the task execution order of your Threlte application. For details on
how to use it, see the [documentation](/docs/learn/basics/scheduling-tasks).
Before, you had the option to choose between `useFrame` and `useRender` to
orchestrate your rendering pipeline. These hooks are currently still available
but will be removed in the next major version of Threlte. This guide will help
you migrate your application to the new Task Scheduling System.

This update also slightly changes the signature of the `<Canvas>` component as
well as the Threlte context.

Also, to increase performance we're enforcing the use of constant prop types
on the `<T>` component.

### Constant prop types on `<T>`

The `<T>` component now enforces the use of _constant prop types_. This means
that the type of a certain prop value must not change in the lifetime of a
component. See this example:

```svelte title="Threlte 6"
<script>
  import { T } from '@threlte/core'

  let position = [0, 0, 0]

  const changePosition = () => {
    position = 1
  }
</script>

<T.Mesh {position} />
```

When `changePosition` is invoked, the prop type of the prop `position` changes
from an array of numbers to a number. This is not allowed anymore in Threlte 7.
Prop types must be constant. It's a highly unlikely scenario that rarely occurs
and a rather bad practice to start with, which allows us to optimize the
performance of the `<T>` component by enforcing this rule. This is how you would
migrate the above example:

```svelte title="Threlte 7" {7}m
<script>
  import { T } from '@threlte/core'

  let position = [0, 0, 0]

  const changePosition = () => {
    position = [1, 1, 1]
  }
</script>

<T.Mesh {position} />
```

### Threlte context

### `<Canvas>` props

#### `frameloop`

`frameloop` is now called `renderMode` as it only affects the rendering of your
Threlte application. It accepts nearly the same values as before:

```ts title="Threlte 6"
<Canvas frameloop="always" />
<Canvas frameloop="demand" />
<Canvas frameloop="never" />
```

```ts title="Threlte 7"
<Canvas renderMode="always" />
<Canvas renderMode="on-demand" />
<Canvas renderMode="manual" />
```

If the value is `always`, Threlte will render your scene on every frame. If the
value is `on-demand`, Threlte will only render your scene when a re-render is
needed. If the value is `manual`, Threlte will never render your scene
automatically and you have to trigger a re-render by calling `advance()` on the
Threlte context available via `useThrelte()`.

#### `autoRender`

When `autoRender` is `false`, Threlte will not render your scene automatically
and will enable you to implement a custom render pipeline using the hook
[`useTask`](/docs/reference/core/use-task). If adding a task to render the scene
to Threlte's
[`renderStage`](/docs/learn/basics/scheduling-tasks#default-stages), the task
will only be called in respect to the `renderMode` prop. Previously, this
behavior was inferred from the usage of the `useRender` hook, but we think being
explicit here is better.

### `useFrame`

The hook [`useTask`](/docs/reference/core/use-task) replaces `useFrame`. It has
a slightly different signature and allows you to to add a `task` to Threlte's
_Task Scheduling System_. A task may have dependencies to other tasks, which you
can think of as the big brother of the `order` option of `useFrame`.

#### Callback Arguments

The callback to `useTask` now only receives the delta time since the last frame.
The Threlte context previously available as the first argument to the callback
of `useFrame` should be retrieved using the hook
[`useThrelte`](/docs/reference/core/use-threlte).

```ts title="Threlte 6"
useFrame(({ camera, scene }, delta) => {
  // The Threlte context was previously available as the first
  // argument to the callback, followed by the delta time since the
  // last frame.
})
```

```ts title="Threlte 7"
const { camera, scene } = useThrelte()
useTask((delta) => {
  // The delta time since the last frame is the only
  // argument to the callback.
})
```

#### `autostart` and `invalidate`

The options of `useTask` have been renamed to better reflect their purpose. The
`autostart` option is now called `autoStart` (note the **capital 'S'**),
`invalidate` is now called `autoInvalidate`.

#### If you didn't use the `order` option

Replace `useFrame` with `useTask` and adapt accessing the Threlte context.

```ts title="Threlte 6"
useFrame(({ camera, scene }, delta) => {
  // ...
})
```

```ts title="Threlte 7"
const { camera, scene } = useThrelte()
useTask((delta) => {
  // ...
})
```

#### If you used the `order` option

Migrate to `useTask` by referencing the key of the task you want to depend on.

```ts title="Threlte 6"
useFrame(
  (_, delta) => {
    // This task will be executed first
  },
  { order: 0 }
)

useFrame(
  (_, delta) => {
    // This task will be executed second
  },
  { order: 1 }
)
```

```ts title="Threlte 7"
useTask('first', (delta) => {
  // ...
})

useTask(
  'second',
  (delta) => {
    // This task will be executed after the task with the
    // key 'first' has been executed.
  },
  { after: 'first' }
)
```

### `useRender`

The hook [`useTask`](/docs/reference/core/use-task) also replaces `useRender`.
Previously, `useRender` allowed you to define a callback that was invoked after
all `useFrame` callbacks have been invoked to render your scene with a custom
render pipeline. This is now possible with `useTask` as well. Threlte provides a
[`renderStage`](/docs/learn/basics/scheduling-tasks#default-stages) that only
ever executes its tasks when a re-render is needed. A task added to this stage
can be used to render your scene. Be sure to set the option `autoInvalidate` to
`false` to prevent Threlte from automatically invalidating the render stage.

```ts title="Threlte 6"
useRender(() => {
  // Render your scene here
})
```

```ts title="Threlte 7"
const { renderStage } = useThrelte()
useTask(
  'render',
  () => {
    // Render your scene here
  },
  { stage: renderStage, autoInvalidate: false }
)
```

#### Callback Arguments

The callback to `useTask` now only receives the delta time since the last frame.
The Threlte context previously available as the first argument to the callback
of `useRender` should be retrieved using the hook
[`useThrelte`](/docs/reference/core/use-threlte).

```ts title="Threlte 6"
useRender(({ camera, scene }, delta) => {
  // The Threlte context was previously available as the first
  // argument to the callback, followed by the delta time since the
  // last frame.
})
```

```ts title="Threlte 7"
const { camera, scene } = useThrelte()
useTask((delta) => {
  // The delta time since the last frame is the only
  // argument to the callback.
})
```

#### If you didn't use the `order` option

Replace `useFrame` with `useTask` and adapt accessing the Threlte context.

```ts title="Threlte 6"
useRender((_{ camera, scene }_, delta) => {
  // ...
})
```

```ts title="Threlte 7"
const { renderStage } = useThrelte()
useTask(
  (delta) => {
    // ...
  },
  { stage: renderStage, autoInvalidate: false }
)
```

#### If you used the `order` option

Migrate to `useTask` by referencing the key of the task you want to depend on.

```ts title="Threlte 6"
useRender(
  (_, delta) => {
    // This task will be executed first
  },
  { order: 0 }
)

useRender(
  (_, delta) => {
    // This task will be executed second
  },
  { order: 1 }
)
```

```ts title="Threlte 7"
const { renderStage } = useThrelte()

useTask(
  'first',
  (delta) => {
    // ...
  },
  { stage: renderStage, autoInvalidate: false }
)

useTask(
  'second',
  (delta) => {
    // This task will be executed after the task with the
    // key 'first' has been executed.
  },
  { after: 'first', stage: renderStage, autoInvalidate: false }
)
```

## Migrating from Threlte 5 to Threlte 6

Threlte 6 provides a much more mature and feature-rich API and developer experience than
its predecessor at the cost of a lot of breaking changes. This guide will help you migrate
your Threlte 5 project to Threlte v6.

### Preprocessing

Preprocessing is not needed anymore starting from Threlte 6. This means you
may remove the preprocessor `@threlte/preprocess` from your project as well as its
configuration in `svelte.config.js`. You can now use the [component `<T>`](/docs/reference/core/t) directly.

### `<Three>` is now `<T>`

Threlte 6 merges the `<Three>` and `<T>` components into a single component. The property `type` was renamed to `is`
to also properly reflect the fact that it can be used with already instantiated objects.

### `@threlte/core` is only about the `<T>` component

The `@threlte/core` package is now only about the `<T>` component. It does not provide any abstractions
that have been part of the core package before. _Some_ of these abstractions (`<TransformControls>`,
`<OrbitControls>`, audio components and several hooks) have been moved to `@threlte/extras` as this is the new
home for commonly used abstractions.

### Prop types

Threlte 6 heavily relies on prop types that Three.js naturally understands. As such, the prop types you may have
previously used to define for example the position of an object changed. Threlte v5 provided its own prop types
`Position` (e.g. `{ x, y, z }`), `Rotation` and others which are now removed or deprecated. While not yet all
abstractions fully make use of the new prop types, we're working on it. Your editor should be able to provide
you with the correct prop types for the components you're using.

### Interactivity

Interactivity is now handled by a plugin that's available at `@threlte/extras`. It's much more mature and flexible
in terms of event handling. For instance – as some of you requested – you may now define on what object the main
event listener is placed. Check out [its documentation](/docs/reference/extras/interactivity) to learn more.

### `useLoader` now returns a store

The hook [`useLoader`](/docs/reference/core/hooks#useloader) now returns a custom Svelte store called
[`AsyncWritable`](/docs/reference/core/utilities#asyncwritable). This store allows you to [await](https://svelte.dev/tutorial/await-blocks)
the loading of the resource while also implementing a regular Svelte store. It also now caches the results
of the loader function so that it's not called multiple times for the same resource. You will most likely
benefit from quite a performance boost in applications that rely heavily on external resources.

### `useThrelteRoot` has been removed

The hook `useThrelteRoot` has been removed and its properties have partially been merged into `useThrelte` as well as
a new internal context which is not exposed. All other contexts (which were used internally) have also been merged or removed.

### `<Pass>` and the default effects rendering are removed

In the effort of clear separation of concerns, the component `<Pass>` as well as the rendering with Three.js default
`EffectComposer` have been removed. Threlte 6 now provides a hook called [`useRender`](/docs/reference/core/hooks#userender) which
allows you to easily set up sophisticated rendering pipelines. As soon as a `useRender` hook is implemented, Threlte's
default render pipeline is disabled. `useRender` callbacks will be invoked _after_ all callback to `useFrame` have been
invoked. This means that you can use `useFrame` to update your objects and `useRender` to render it. `useRender` also has the option of
ordering callbacks to orchestrate the rendering pipeline across multiple components.

### Threlte's main context types

Thelte's main context contains Svelte stores. These stores are now a custom Threlte store called
[`CurrentWritable`](/docs/reference/core/utilities#currentwritable) which is a store that contains a `current` value with
a reference to the current value of the store. This means it does not need to be unwrapped manually (and expensively!) in
non-reactive places such as loops. For instance, let's have a look at its usage in the hook
[`useFrame`](/docs/reference/core/hooks#useframe) where the context is available as the first argument
to the callback:

```ts
useFrame(({ camera, colorSpace }) => {
  // instead of get(camera) we now can …
  camera.current // THREE.Camera
  colorSpace.current // THREE.ColorSpace
})
```

The full type definition is [currently listed here](/docs/reference/core/hooks#usethrelte).

### `useGltfAnimations` Signature

The signature of the hook `useGltfAnimations` has changed. It no longer provides a callback that is invoked
when the `gltf` store has been populated and the `actions` store has been set. This is because it with the option to set
a custom root for the `THREE.AnimationAction`, the callback could be triggered multiple times, leading to an
unpredictable behavior. You should reside to using the `actions` store returned from the hook instead.

```ts
const { actions } = useGltfAnimations(gltf)
// this animation will play when the gltf store has been populated
// and the actions store has been set, effectively replacing the
// callback.
$: $actions.Greet?.play()
```

Check out the [hooks documentation](/docs/reference/extras/use-gltf-animations) for more information.

### `@threlte/rapier`

#### Transform props

In an effort to clearly separate concerns, the components `<Collider>`, `<AutoColliders>` and `<RigidBody>` **no longer
offer transform props** (`position`, `rotation`, `scale` and `lookAt`). Instead, you should wrap these components
in for instance `<T.Group>` components and apply transforms on these.

```svelte title="Before.svelte" {2,3}-
<Collider
  position={[0, 1, 0]}
  rotation={[0, 45 * DEG2RAD, 0]}
>
  <T.Mesh>
    <T.BoxGeometry />
    <T.MeshStandardMaterial />
  </T.Mesh>
</Collider>
```

```svelte title="After.svelte" {1-4,11}+
<T.Group
  position={[0, 1, 0]}
  rotation={[0, 45 * DEG2RAD, 0]}
>
  <Collider>
    <T.Mesh>
      <T.BoxGeometry />
      <T.MeshStandardMaterial />
    </T.Mesh>
  </Collider>
</T.Group>
```
